
from twisted.web.resource import Resource
from twisted.application import strports
from twisted.web import server, distrib
from twisted.web import html as twhtml
from twisted.python import log as twistedlog

import urllib, time, re

from buildbot import interfaces
from buildbot.status import html
from buildbot.status.builder import FAILURE, SUCCESS, WARNINGS

class XmlResource(Resource):
    contentType = "text/xml; charset=UTF-8"
    def render(self, request):
        data = self.content(request)
        request.setHeader("content-type", self.contentType)
        if request.method == "HEAD":
            request.setHeader("content-length", len(data))
            return ''
        return data
    docType = ''
    def header (self, request):
        data = ('<?xml version="1.0"?>\n')
        return data
    def footer(self, request):
        data = ''
        return data 
    def content(self, request):
        data = self.docType
        data += self.header(request)
        data += self.body(request)
        data += self.footer(request)
        return data
    def body(self, request):
        return ''

class FeedResource(XmlResource):
    title = 'Dummy'
    link = 'http://dummylink'
    language = 'en'
    description = 'Dummy rss'
    status = None

    def __init__(self, status, changemaster, categories):
        self.status = status
        self.changemaster = changemaster
        self.categories = categories
        self.link = self.status.getBuildbotURL()
        self.title = 'Build status of ' + status.getProjectName()
        self.description = 'List of FAILED builds'

    def getBuilds(self, request):
        builds = []
        showBuilders = request.args.get('show', '')
        if showBuilders:
            builderNames = [b for b in self.status.getBuilderNames(None) 
                if b in showBuilders]
        else:
		    builderNames = self.status.getBuilderNames(None)
        builders = map(lambda name: self.status.getBuilder(name), builderNames)
        maxFeeds = 25
        
        # Copy all failed builds in a new list.
        # This could clearly be implemented much better if we had access to a global list
        # of builds.
        for b in builders:
            lastbuild = b.getLastFinishedBuild()
            if lastbuild is None:
                continue

            lastnr = lastbuild.getNumber()

            totalbuilds = 0
            i = lastnr          
            while i >= 0:
                build = b.getBuild(i)
                i -= 1
                if not build:
                    continue

                results = build.getResults()

                # only add entries for failed builds!
                if results == FAILURE:
                    totalbuilds += 1
                    builds.append(build)

                # stop for this builder when our total nr. of feeds is reached
                if totalbuilds >= maxFeeds:
                    break

        # Sort build list by date, youngest first.
        # We apply Decorate-Sort-Undecorate
        deco = [(build.getTimes(), build) for build in builds]
        deco.sort()
        deco.reverse()
        builds = [build for (b1, build) in deco]
        #builds.sort(key=lambda build: build.getTimes(), reverse=True)
        
        if builds:
            builds = builds[:min(len(builds), maxFeeds)]
        return builds

    def body (self, request):
        data = ''
        builds = self.getBuilds(request)

        for build in builds:
            start, finished = build.getTimes()
            strFinished = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.localtime(int(finished)))
            link = self.status.getURLForThing(build)
            url = self.status.getBuildbotURL()

            # GG: add branch name to URL
            # branch name is last word in builder name
            #branch = ""
            #s = re.search("%s(.*)/builds" % url, link)
            #if s:
            #    buildername = s.group(1)
            #    twistedlog.msg("Found buildername=%s" % buildername)
            #    percent20 = buildername.split("%20")
            #    branch = percent20[-1]
            #if branch:
            #    # add branch name after builbot URL
            #    link = re.sub(url, "%s%s/" % (url, branch), link)

            # title: trunk r22191 (plus patch) failed on 'i686-debian-sarge1 shared gcc-3.3.5'
            ss = build.getSourceStamp()
            if ss is None:
                source = "[src unknown]"
            else:
                branch, revision, patch = ss
                if build.getChanges():
                    revision = max([int(c.revision) for c in build.getChanges()])
                source = ""
                if branch:
                    source += "Branch %s " % branch
                if revision:
                    source += "r%s " % str(revision)
                else:
                    source += "HEAD"
                if patch is not None:
                    source += " (plus patch)"
            title = source + " failed on '" + build.getSlavename() + "'"
            
            # get name of the first failed step and the last 30 lines of its log.
            failedlog = ""
            failedstep = ""
            steps = build.getSteps()
            for step in steps:
                result, strings = step.getResults()
                if result == FAILURE:
                    log = step.getLogs()[-1]
                    failedlog = log.getText()
                    failedstep = step.getName()
                    break

            lines = re.split('\n', failedlog)
            failedlog = ''
            for logline in lines[max(0, len(lines)-30):]:
                failedlog = failedlog + logline + '<br/>'

            description = '<![CDATA['
            description += 'Date: ' + strFinished + '<br/><br/>'
            description += 'Full details are available at: <br/>'
            description += '<a href="' + link + '">' + link + '</a><br/><br/>'
            description += 'Author list: <b>' + ",".join(build.getResponsibleUsers()) + '</b><br/><br/>'
            description += 'First failed step: <b>' + failedstep + '</b><br/><br/>'
            description += 'Last lines of the build log for the failed step:<br/>'
            description += failedlog.replace('\n', '<br/>')
            description += ']]>'

            data += self.item(title,
                              description = description,
                              link=link,pubDate=strFinished)           

        return data

    def item(self, title='', link='', description='', pubDate=''):
        """Generates xml for one item in the feed"""

class Rss20StatusResource(FeedResource):
    def __init__(self, status, changemaster, categories):
        FeedResource.__init__(self, status, changemaster, categories)
        contentType = 'application/rss+xml'

    def header(self, request):
        data = FeedResource.header(self, request)
        data += '<rss version="2.0">\n'
        data += '<channel>'
        if self.title is not None:
            data += '<title>'+self.title+'</title>'
        if self.link is not None:
            data += '<link>'+self.link+'</link>'
        if self.language is not None:
            data += '<language>'+self.language+'</language>'
        if self.description is not None:
            data += '<description>'+self.description+'</description>'
        return data    

    def item(self, title='', link='', description='', pubDate=''):
        data = '<item>'
        data += '<title>'+title+'</title>'
        if link is not None:
            data += '<link>'+link+'</link>'
        if description is not None:
            data += '<description>'+ description + '</description>'
        if pubDate is not None:
            data += '<pubDate>'+pubDate+'</pubDate>'
        data += '</item>'
        return data

    def footer(self, request):
        data = ('</channel>'
                '</rss>')
        return data

class Atom10StatusResource(FeedResource):
    def __init__(self, status, changemaster, categories):
        FeedResource.__init__(self, status, changemaster, categories)
        contentType = 'application/atom+xml'

    def header(self, request):
        data = FeedResource.header(self, request)
        data += '<feed xmlns="http://www.w3.org/2005/Atom">\n'
        if self.title is not None:
            data += '<title>'+self.title+'</title>'
        if self.link is not None:
            data += '<link href="'+self.link+'"/>'
        # if self.language is not None:
            # data += '<language>'+self.language+'</language>'
        if self.description is not None:
            data += '<subtitle>'+self.description+'</subtitle>'
        return data    

    def item(self, title='', link='', description='', pubDate=''):
        data = '<entry>'
        data += '<title>'+title+'</title>'
        if link is not None:
            data += '<link href="'+link+'"/>'
        if description is not None:
            data += '<summary type="html">'+ description + '</summary>'
        if pubDate is not None:
            data += '<updated>'+pubDate+'</updated>'
        data += '</entry>'
        return data

    def footer(self, request):
        data = ('</feed>')
        return data

class WaterfallWithFeeds(html.Waterfall):
    """ Override the standard Waterfall class to add RSS and Atom feeds """

    def setup(self):
        html.Waterfall.setup(self)

        status = self.parent.getStatus()
        change_svc = self.parent.change_svc
        sr = self.site.resource

        rss = Rss20StatusResource(status, change_svc, self.categories)
        sr.putChild("rss", rss)
        atom = Atom10StatusResource(status, change_svc, self.categories)
        sr.putChild("atom", atom)
